﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Web.Script.Serialization;
using KsWare.Breconium.Core.ProtocollData;

namespace KsWare.Breconium.Core {

	public class Session:ISession {
		private SyncronizedList<Command> mCommand1Queue=new SyncronizedList<Command>();
		private SyncronizedList<Command> mCommand2Queue=new SyncronizedList<Command>();
		private SyncronizedList<Command> mCommand3Queue=new SyncronizedList<Command>();
		private int mCommandPollState;
		private DateTime mLastCommandPoll0;
		private DateTime mLastCommandPoll1;
		private long mLastMessageId;

		public Session(BreconiumServer server) {
			Server  = server;
			Id      = Guid.NewGuid().ToString("N");
			Created = DateTime.Now;
		}

		public string Id { get; set; }

		public DateTime Created { get; set; }

		public string Location { get; set; }
		public BreconiumServer Server { get; private set; }


		internal Command PeekCommand(int queueId, long? messageId, TimeSpan timeoutTime, bool throwTimeoutException) {
			return GetCommand(queueId, false, messageId, timeoutTime, throwTimeoutException);
		}
		internal Command DequeueCommand(int queueId, long? messageId, TimeSpan timeoutTime, bool throwTimeoutException) {
			return GetCommand(queueId, true, messageId, timeoutTime, throwTimeoutException);
		}

		internal Command GetCommand(int queueId, bool remove,  long? messageId, TimeSpan timeoutTime, bool throwTimeoutException) {
			Command command=null;

			SyncronizedList<Command> queue;
			switch (queueId) {
				case 1:queue = mCommand1Queue;break;
				case 2:queue = mCommand2Queue;break;
				case 3:queue = mCommand3Queue;break;
				default: throw new ArgumentOutOfRangeException("queueId");
			}

			var tot = DateTime.Now.Add(timeoutTime);
			while (true) {
//				lock (queue) {
					if(messageId.HasValue) {
						foreach (var c in queue) {
							if(c.Message.MID==messageId.Value) {
								command = c; 
								if(remove) queue.Remove(c); 
								break;
							}
						}
					} else {
						if(queue.Count>0) {
							command = queue[0]; 
							if(remove) queue.RemoveAt(0);
						}
					}
//				}
				if(command!=null) break;
				if(timeoutTime==TimeSpan.Zero) return null;
				if(DateTime.Now>=tot) {
					if(throwTimeoutException) throw new TimeoutException();
					return null;
				}
				//if(queueId==1 && mLastCommandPoll1<DateTime.Now.AddMilliseconds(-5000)) {}
				Thread.Sleep(100);
			}

			return command;
		}
		
		internal void EnqueueCommand(int queueId, Command command) {
			command.Message.SID = this.Id;
			SyncronizedList<Command> queue;
			switch (queueId) {
				case 1:queue = mCommand1Queue;break;
				case 2:queue = mCommand2Queue;break;
				case 3:queue = mCommand3Queue;break;
				default: throw new ArgumentOutOfRangeException("queueId");
			}
//			lock (queue) {
				queue.Add(command);
//				Monitor.Pulse(queue);
//			}
			switch (queueId) {
				case 1: command.State=CommandState.S1_Enqueued; break;
				case 2: command.State=CommandState.S2_Sent;break;
				case 3: command.State=CommandState.S3_ResponseReceived;break;
			}
			if(queueId==3) OnCommandResponseReceived(command);
		}

		private void OnCommandReceived(Command command) {
			if(CommandReceived==null)return;
			CommandReceived(this, new CommandEventArgs(command));
		}

		private void OnCommandResponseReceived(Command command) {
			if(CommandResponseReceived==null)return;
			CommandResponseReceived(this, new CommandEventArgs(command));
		}

		public void SendCommand(Command command) {
			command.Message=new MessageData(){
				SID = Id,
				MID = NewMessageId()
			};
			EnqueueCommand(1,command);
		}

		private long NewMessageId() { return ++mLastMessageId; }

		public object ExecuteCommand(Command command) { return ExecuteCommand(command, TimeSpan.FromMilliseconds(30000)); }

		public object ExecuteCommand(Command command,TimeSpan timeout) {
			try {
				SendCommand(command);
				DequeueCommand(3, command.Message.MID, timeout, true);
			} catch(Exception ex) {
				var cmd=DequeueCommand(1, command.Message.MID, TimeSpan.Zero, false);
				if(cmd==null) cmd=DequeueCommand(2, command.Message.MID, TimeSpan.Zero, false);
				throw;
			}finally {}
			if(command.HasError) {
				throw new CommandException(command.Error);
			}else {
				return command.Result;
			}
		}

		public event EventHandler<CommandEventArgs> CommandReceived;

		public event EventHandler<CommandEventArgs> CommandResponseReceived;

	}



	public interface ISession
	{
		object ExecuteCommand(Command command);

//		IAsyncResult BeginCommand(Command command);
//
//		object EndCommand(IAsyncResult asyncResult);

		event EventHandler<CommandEventArgs> CommandReceived;

		event EventHandler<CommandEventArgs> CommandResponseReceived;
	}

	public class CommandEventArgs:EventArgs {
		public CommandEventArgs(Command command) { Command = command; }
		public Command Command { get; private set; }
	}
}

/*
var js = new JavaScriptSerializer();
dynamic dynObj =  js.DeserializeObject(jsonN);
foreach (var obj in dynObj)
{
    if (obj.ContainsKey("name")) Console.WriteLine(obj["name"]);
    else Console.WriteLine(obj["message"]);
}
*/